package com.liujun.io.nio.mysql.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

import com.liujun.io.nio.mysql.bean.BytesBufferAttachMsg;
import com.liujun.io.nio.mysql.bean.SocketDataTran;
import com.liujun.io.nio.mysql.trans.DataParseProcess;
import com.liujun.io.nio.mysql.trans.TransDataManager;
import com.liujun.io.nio.trans.console.Config;
import com.liujun.io.nio.trans.console.ParamMsg;
import com.liujun.io.nio.trans.util.PropertiesUtils;

/**
 * 用于构造中间件服务器与mysql之间的通道
* 源文件名：MultiConnectionervice.java
* 文件版本：1.0.0
* 创建作者：liujun
* 创建日期：2016年8月24日
* 修改作者：liujun
* 修改日期：2016年8月24日
* 文件描述：liujun
* 版权所有：Copyright 2016 zjhz, Inc. All Rights Reserved.
*/
public class MultMidToMysqlServerConnection implements Runnable {

    private static final int OP_NOT_WRITE = ~SelectionKey.OP_WRITE;

    /**
     * 连接的多路选择器
     */
    private Selector connSelector;

    /**
     * 用于进行通信的通道
    * @字段说明 socketChannel
    */
    private SocketChannel sc;

    /**
     * 用来控制整个服务器的开始操作,true,表示开启，false 关闭
     */
    private volatile boolean stop = false;

    /**
     * 用来进行数据的管理
    * @字段说明 transConn
    */
    private final TransDataManager transConn;

    /**
     * 服务端到中间数据写入
     * @字段说明 process
     */
    private final DataParseProcess<SocketDataTran> serviceToMidCollection;

    /**
     * 客户端向服务端写入数据
    * @字段说明 clientToMysqlServer
    */
    private final DataParseProcess<SocketDataTran> clientToMysqlServer;

    /**
     * mysql服务器端的ip
     * 
     * @字段说明 serverHost
     */
    private String serverMysqlHost = PropertiesUtils.getInstance().getValue(ParamMsg.PROPERTIS_SERVER_IP.getKey());

    /**
     * mysql服务器端的端口
     * 
     * @字段说明 serverHost
     */
    private int serverMysqlPort = Integer
            .parseInt(PropertiesUtils.getInstance().getValue(ParamMsg.PROPERTIS_SERVER_PORT.getKey()));

    /**
    * 构造方法
    * @param connSelector 连接选择器
    * @param transConn 通道管理器
    * @param processSelect 处理选择器
    */
    public MultMidToMysqlServerConnection(Selector connSelector, TransDataManager transConn,
            DataParseProcess<SocketDataTran> process, DataParseProcess<SocketDataTran> clientToMysqlServer) {
        this.connSelector = connSelector;
        this.transConn = transConn;
        this.serviceToMidCollection = process;
        this.clientToMysqlServer = clientToMysqlServer;

        try {
            // 打开客户端流通道
            sc = SocketChannel.open();

            // 设置为非阻塞模式
            sc.configureBlocking(false);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public void stop() {
        stop = true;
    }

    @Override
    public void run() {

        // 向数据发送连接请求，以获得响应报文
        // 生成通道信息
        int id = transConn.regConnChannel();

        // 将本地代理连接mysql的信息附着到连接mysql的通道中
        BytesBufferAttachMsg attachMysql = new BytesBufferAttachMsg(Config.CONFIG_TYPE_END.getKey());
        attachMysql.setId(id);
        // 将本地代理服务端的信息附着到通道中
        ByteBuffer byteBuffer = transConn.getFileChannel(id);
        attachMysql.setByteBuffer(byteBuffer);
        this.openNewSocketChann(serverMysqlHost, serverMysqlPort, connSelector, attachMysql);

        // 如果当前开始不是关闭的才能运行
        while (!stop) {
            try {
                // 在此通道阻塞1000毫秒
                int readyChannels = connSelector.select(100);

                if (readyChannels == 0) {
                    continue;
                }
                // // 在此通道阻塞1000毫秒
                // connSelector.select(100);

                // 轮循进行检查是否有已经准备就绪的key
                Set<SelectionKey> selectkey = connSelector.selectedKeys();

                Iterator<SelectionKey> iter = selectkey.iterator();

                while (iter.hasNext()) {
                    SelectionKey sekey = iter.next();

                    // 将当前的数据交给对应的方法来处理
                    try {
                        this.handleInput(sekey);
                    } catch (Exception e) {
                        e.printStackTrace();
                        if (null != sekey) {
                            sekey.cancel();
                            if (sekey.channel() != null) {
                                sekey.channel().close();
                            }
                        }
                    }
                    iter.remove();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 进行通道的处理操作 方法描述
     * 
     * @param key
     * @throws IOException
     * @创建日期 2016年8月24日
     */
    private void handleInput(SelectionKey key) throws IOException {
        // 如果当前通道是有效的
        if (key.isValid()) {

            // 如果为连接信息
            if (key.isConnectable()) {
                SocketChannel sc = (SocketChannel) key.channel();
                // 如果已经完成连接
                if (sc.finishConnect()) {
                    BytesBufferAttachMsg attach = (BytesBufferAttachMsg) key.attachment();
                    sc.register(connSelector, SelectionKey.OP_READ, attach);
                }
            }

            // 检查此键的通道是否已准备好进行读取
            if (key.isReadable()) {
                // 得到当前
                SocketChannel sc = (SocketChannel) key.channel();

                BytesBufferAttachMsg type = (BytesBufferAttachMsg) key.attachment();

                // 如果前端连接
                if (Config.CONFIG_TYPE_FIRST.getKey() == type.getKey()) {
                    // 将A通道中的数据加载的内存A
                    sc.read(type.getByteBuffer());

                    // 放入的数据
                    // serviceToMidCollection.putValue(type.getByteBuffer());
                }
                // 如果为后端连接
                else if (Config.CONFIG_TYPE_END.getKey() == type.getKey()) {

                    int rsp = sc.read(type.getByteBuffer());
                    System.out.println("收到mysql响应字节:" + rsp);

                    if (rsp >= 0) {
                        // System.out.println("收到mysql响应字节:" + rsp);
                        SocketDataTran dataTran = new SocketDataTran(type.getByteBuffer(), key);
                        // 放入第一次的数据
                        serviceToMidCollection.putValue(dataTran);
                    }

                }
            }
            if (key.isWritable()) {

                // 得到当前
                SocketChannel sc = (SocketChannel) key.channel();

                BytesBufferAttachMsg type = (BytesBufferAttachMsg) key.attachment();

                // 如果为后端连接
                if (Config.CONFIG_TYPE_END.getKey() == type.getKey()) {

                    SocketDataTran data = clientToMysqlServer.getValue();

                    if (null != data) {

                        // 调整为写模式进行数据写入
                        data.getBuffer().flip();
                        // 进行第一次的数据的写入
                        int size = sc.write(data.getBuffer());
                        data.getBuffer().clear();

                        // 注册一个非写入事件
                        key.interestOps(key.interestOps() & OP_NOT_WRITE);
                        System.out.println("写入结果大小:" + size);
                    }

                }
            }

        }
        // 如果此键已经失效，则关闭连接
        else {
            key.cancel();
            key.channel().close();
        }
    }

    public SocketChannel getChannel() {
        return sc;
    }

    /**
     * 创建连接信息 方法描述
     * 
     * @param host
     * @param port
     * @param selector
     * @创建日期 2016年8月24日
     */
    public void openNewSocketChann(String host, int port, Selector selector, Object attach) {
        try {

            // 如果服用服务器的连接已经连接成功，需要将读取消息注册到多路复用器上，然后发送消息
            if (sc.connect(new InetSocketAddress(host, port))) {
                // 注册读取操作到多路复用器上
                sc.register(selector, SelectionKey.OP_WRITE, attach);
            } else {
                // 注册连接
                sc.register(selector, SelectionKey.OP_CONNECT, attach);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
